package scf.demo.feign;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SmUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import scf.demo.config.DemoConfig;

import java.time.Instant;
import java.util.TreeMap;

@Slf4j
public class FeignConfig {

    public static final String MSG_TPL = """
            salt:{"ProductId":"{}","Nonce":{},"Timestamp":{},"AppSecret":"{}"}\
            """;

    @Autowired
    private DemoConfig demoConfig;

    @Autowired
    private ObjectMapper objectMapper;

    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

    @Bean
    public ObjectFactory<HttpMessageConverters> getConverters(MappingJackson2HttpMessageConverter converter) {
        return () -> new HttpMessageConverters(converter);
    }

    @Bean
    public RequestInterceptor requestInterceptor() {
        return this::sign;
    }

    /**
     * 请求签名
     *
     * @param request 请求
     */
    private void sign(RequestTemplate request) {
        var productId = CollUtil.getFirst(request.headers().get("ProductId"));
        Assert.notEmpty(productId, "请求头: ProductId 缺失");
        var secret = demoConfig.mySecret(productId);
        Assert.notEmpty(secret, "{}的密钥配置内容缺失", productId);
        // 初始化消息
        var nonce = RandomUtil.randomInt();
        var timestamp = Instant.now().getEpochSecond();
        request.header("Nonce", String.valueOf(nonce));
        request.header("Timestamp", String.valueOf(timestamp));
        var message = StrUtil.format(MSG_TPL, productId, nonce, timestamp, secret);
        // 若请求包含查询参数(GET)，则将其加入消息内容，虽然POST方法可以有查询参数，但推荐POST方法的所有的参数都在请求体中
        if (ObjUtil.isNotEmpty(request.queries())) {
            message += ",query:" + MapUtil.sortJoin(request.queries(), "&", "=", false);
        }
        // 若请求包含请求体(POST)，则将其加入消息内容
        if (ObjUtil.isNotEmpty(request.body())) {
            message += ",body:" + sortBody(new String(request.body()));
        }
        log.debug("消息: {}", message);
        // 签名消息
        var signature = SmUtil.sm3().digestHex(message);
        log.debug("签名: {}", signature);
        request.header("Signature", signature);
    }

    @SneakyThrows
    private String sortBody(String body) {
        TreeMap treeMap = objectMapper.readValue(body, TreeMap.class);
        return objectMapper.writeValueAsString(treeMap);
    }

}
